---
title: Loaders
---

Loaders are useful for one-time loading of data from the server to the client and can only be used in routes in your `app` directory.

Loaders run on the server, for now they run based on their render mode - during build-time for SSG routes, or on each request for SPA or SSR routes.

## Native Platform Behavior

Loaders work similarly on native platforms as they do on web:

- **SSG routes**: Data is fetched at compile-time and included in the bundle
- **SSR routes**: Data is fetched from the server on each load
- **SPA routes**: Data is fetched from the server on each load

This means native apps can use the same loader patterns as web apps, with data fetching behavior determined by the route's render mode rather than the platform.

Loaders and their imports are removed from client bundles, so you can access private information from within the loader. The data returned from the loader will be passed to the client, and so should be clear of private information.

## Accessing Loader Data

There are two hooks available for accessing loader data:

### useLoader

The basic hook for accessing loader data:

```tsx
import { useLoader } from 'one'

export async function loader() {
  return {
    user: 'tamagui'
  }
}

export default function HomePage() {
  const data = useLoader(loader)

  return (
    <p>
      {data.user}
    </p>
  )
}
```

### useLoaderState

For advanced use cases requiring manual refetch or loading states:

```tsx
import { useLoaderState } from 'one'

export async function loader() {
  return {
    user: 'tamagui'
  }
}

export default function HomePage() {
  const { data, refetch, state } = useLoaderState(loader)

  return (
    <>
      <p>{data.user}</p>
      <button onClick={refetch} disabled={state === 'loading'}>
        Refresh
      </button>
    </>
  )
}
```

Both hooks are automatically type safe. See [useLoader](/docs/hooks-useLoader) and [useLoaderState](/docs/hooks-useLoaderState) for detailed documentation.

## Loader arguments

Loaders receive a single argument object:

### params

 The `params` key will provide values from any dynamic route segments:

```tsx fileName=app/user/[id].tsx
export async function loader({ params }) {
  // for route /user/jamon params.id is a string "jamon"
  const user = await getUser(params.id)
  
  return {
    greet: `Hello ${user.name}`
  }
}
```

### path

The `path` key is the fully resolved pathname:

```tsx fileName=app/user/[id].tsx
export async function loader({ path }) {
  // if the route is /user/123 then path is "/user/123"
}
```

### request

Only for [`ssr` type routes](/docs/routing-modes#ssr). This will pass the same [Web standard Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) as API routes.

## Accepted Return Types

Most JavaScript values, including primitives and objects, will be converted to JSON.

You may also return a `Response`:

```tsx
export async function loader({ params: { id } }) {
  const user = await db.users.findOne({ id })
  const body = JSON.stringify(user)
  return new Response(body, {
    headers: {
      'Content-Type': 'application/json',
    },
  })
}
```

## Throwing a Response

A final handy pattern for loaders is throwing a response to end it early:

```tsx
export async function loader({ params: { id } }) {
  const user = await db.users.findOne({ id })

  if (!user) {
    throw Response.error()
  }
  
  // ... rest of function
}
```

You can combine this with the [redirect utility function](/docs/helpers-utility-functions#redirect):

```tsx
import { redirect } from 'one'

export async function loader({ params: { id } }) {
  const user = await db.users.findOne({ id })

  if (!user) {
    throw redirect('/login')
  }

  // ... rest of function
}
```
